import * as Cesium from "cesium";
import Sandcastle from "Sandcastle";

// San Francisco Ferry Building photogrammetry model provided by Aerometrex
const viewer = new Cesium.Viewer("cesiumContainer", {
  infoBox: false,
  orderIndependentTranslucency: false,
  terrain: Cesium.Terrain.fromWorldTerrain(),
});

viewer.clock.currentTime = Cesium.JulianDate.fromIso8601(
  "2021-11-09T20:27:37.016064475348684937Z",
);

const scene = viewer.scene;

// Fly to a nice overview of the city.
viewer.camera.flyTo({
  destination: new Cesium.Cartesian3(
    -2703640.80485846,
    -4261161.990345464,
    3887439.511104276,
  ),
  orientation: new Cesium.HeadingPitchRoll(
    0.22426651143535548,
    -0.2624145362506527,
    0.000006972977223185239,
  ),
  duration: 0,
});

let tileset;
try {
  tileset = await Cesium.Cesium3DTileset.fromIonAssetId(2333904);

  const translation = new Cesium.Cartesian3(
    -1.398521324920626,
    0.7823052871729486,
    0.7015244410592609,
  );
  tileset.modelMatrix = Cesium.Matrix4.fromTranslation(translation);

  tileset.maximumScreenSpaceError = 8.0;
  scene.pickTranslucentDepth = true;
  scene.light.intensity = 7.0;

  viewer.scene.primitives.add(tileset);
} catch (error) {
  console.log(`Error loading tileset: ${error}`);
}

// Styles =============================================================================

const classificationStyle = new Cesium.Cesium3DTileStyle({
  color: "color(${color})",
});

const translucentWindowsStyle = new Cesium.Cesium3DTileStyle({
  color: {
    conditions: [["${component} === 'Windows'", "color('gray', 0.7)"]],
  },
});

// Shaders ============================================================================

// Dummy shader that sets the UNLIT lighting mode. For use with the classification style
const emptyFragmentShader =
  "void fragmentMain(FragmentInput fsInput, inout czm_modelMaterial material) {}";
const unlitShader = new Cesium.CustomShader({
  lightingModel: Cesium.LightingModel.UNLIT,
  fragmentShaderText: emptyFragmentShader,
});

const materialShader = new Cesium.CustomShader({
  lightingModel: Cesium.LightingModel.PBR,
  fragmentShaderText: `
      const int WINDOW = 0;
      const int FRAME = 1;
      const int WALL = 2;
      const int ROOF = 3;
      const int SKYLIGHT = 4;
      const int AIR_CONDITIONER_WHITE = 5;
      const int AIR_CONDITIONER_BLACK = 6;
      const int AIR_CONDITIONER_TALL = 7;
      const int CLOCK = 8;
      const int PILLARS = 9;
      const int STREET_LIGHT = 10;
      const int TRAFFIC_LIGHT = 11;

      void fragmentMain(FragmentInput fsInput, inout czm_modelMaterial material) {
        int featureId = fsInput.featureIds.featureId_0;

        if (featureId == CLOCK) {
          // Shiny brass
          material.specular = vec3(0.98, 0.90, 0.59);
          material.roughness = 0.1;
        } else if (
          featureId == STREET_LIGHT ||
          featureId == AIR_CONDITIONER_BLACK ||
          featureId == AIR_CONDITIONER_WHITE ||
          featureId == AIR_CONDITIONER_TALL ||
          featureId == ROOF
        ) {
          // dull aluminum
          material.specular = vec3(0.91, 0.92, 0.92);
          material.roughness = 0.5;
        } else if (featureId == WINDOW || featureId == SKYLIGHT) {
          // make translucent, but also set an orange emissive color so it looks like
          // it's lit from inside
          material.emissive = vec3(1.0, 0.3, 0.0);
          material.alpha = 0.5;
        } else if (featureId == WALL || featureId == FRAME || featureId == PILLARS) {
          // paint the walls and pillars white to contrast the brass clock
          material.diffuse = mix(material.diffuse, vec3(1.0), 0.8);
          material.roughness = 0.9;
        } else {
          // brighten everything else
          material.diffuse += 0.05;
          material.roughness = 0.9;
        }
      }
      `,
});

const NOTHING_SELECTED = 12;
const selectFeatureShader = new Cesium.CustomShader({
  uniforms: {
    u_selectedFeature: {
      type: Cesium.UniformType.INT,
      value: NOTHING_SELECTED,
    },
  },
  lightingModel: Cesium.LightingModel.PBR,
  fragmentShaderText: `
      const int NOTHING_SELECTED = 12;
      void fragmentMain(FragmentInput fsInput, inout czm_modelMaterial material) {
        int featureId = fsInput.featureIds.featureId_0;

        if (u_selectedFeature < NOTHING_SELECTED && featureId == u_selectedFeature) {
          material.specular = vec3(1.00, 0.85, 0.57);
          material.roughness = 0.1;
        }
      }
      `,
});

const multipleFeatureIdsShader = new Cesium.CustomShader({
  uniforms: {
    u_selectedFeature: {
      type: Cesium.UniformType.FLOAT,
      value: NOTHING_SELECTED,
    },
  },
  lightingModel: Cesium.LightingModel.UNLIT,
  fragmentShaderText: `
      const int IDS0_WINDOW = 0;
      const int IDS1_FACADE = 2;
      const int IDS1_ROOF = 3;
      const vec3 PURPLE = vec3(0.5, 0.0, 1.0);
      const vec3 YELLOW = vec3(1.0, 1.0, 0.0);
      const vec3 NO_TINT = vec3(1.0);

      void fragmentMain(FragmentInput fsInput, inout czm_modelMaterial material) {
        int featureId0 = fsInput.featureIds.featureId_0; // fine features
        int featureId1 = fsInput.featureIds.featureId_1; // coarse features

        // use both feature ID sets to determine where the features are
        float isWindow = float(featureId0 == IDS0_WINDOW);
        float isFacade = float(featureId1 == IDS1_FACADE);
        float isRoof = float(featureId1 == IDS1_ROOF);

        // Tint the roof windows yellow and facade windows purple
        vec3 tint = NO_TINT;
        tint = mix(tint, YELLOW, isWindow * isRoof);
        tint = mix(tint, PURPLE, isWindow * isFacade);
        material.diffuse *= tint;
      }
      `,
});

// Demo Functions =====================================================================

function defaults() {
  tileset.style = undefined;
  tileset.customShader = unlitShader;
  tileset.colorBlendMode = Cesium.Cesium3DTileColorBlendMode.HIGHLIGHT;
  tileset.colorBlendAmount = 0.5;
  tileset.featureIdLabel = 0;
}

const showPhotogrammetry = defaults;

function showClassification() {
  defaults();
  tileset.style = classificationStyle;
  tileset.colorBlendMode = Cesium.Cesium3DTileColorBlendMode.MIX;
}

function showAlternativeClassification() {
  showClassification();
  // This dataset has a second feature ID texture.
  tileset.featureIdLabel = 1;
}

function translucentWindows() {
  defaults();
  tileset.style = translucentWindowsStyle;
}

function pbrMaterials() {
  defaults();
  tileset.customShader = materialShader;
}

function goldenTouch() {
  defaults();
  tileset.customShader = selectFeatureShader;
}

function multipleFeatureIds() {
  defaults();
  tileset.customShader = multipleFeatureIdsShader;
}

// Pick Handlers ======================================================================

// HTML overlay for showing feature name on mouseover
const nameOverlay = document.createElement("div");
viewer.container.appendChild(nameOverlay);
nameOverlay.className = "backdrop";
nameOverlay.style.display = "none";
nameOverlay.style.position = "absolute";
nameOverlay.style.bottom = "0";
nameOverlay.style.left = "0";
nameOverlay.style["pointer-events"] = "none";
nameOverlay.style.padding = "4px";
nameOverlay.style.backgroundColor = "black";
nameOverlay.style.whiteSpace = "pre-line";
nameOverlay.style.fontSize = "12px";

let enablePicking = true;
const handler = new Cesium.ScreenSpaceEventHandler(viewer.scene.canvas);
handler.setInputAction(function (movement) {
  if (enablePicking) {
    const pickedObject = viewer.scene.pick(movement.endPosition);
    if (pickedObject instanceof Cesium.Cesium3DTileFeature) {
      nameOverlay.style.display = "block";
      nameOverlay.style.bottom = `${
        viewer.canvas.clientHeight - movement.endPosition.y
      }px`;
      nameOverlay.style.left = `${movement.endPosition.x}px`;
      const component = pickedObject.getProperty("component");
      const message = `Component: ${component}\nFeature ID: ${pickedObject.featureId}`;
      nameOverlay.textContent = message;
    } else {
      nameOverlay.style.display = "none";
    }
  } else {
    nameOverlay.style.display = "none";
  }
}, Cesium.ScreenSpaceEventType.MOUSE_MOVE);

const clickHandler = new Cesium.ScreenSpaceEventHandler(scene.canvas);
clickHandler.setInputAction(function (movement) {
  if (enablePicking) {
    const pickedObject = scene.pick(movement.position);
    if (
      Cesium.defined(pickedObject) &&
      Cesium.defined(pickedObject.featureId)
    ) {
      selectFeatureShader.setUniform(
        "u_selectedFeature",
        pickedObject.featureId,
      );
    } else {
      selectFeatureShader.setUniform("u_selectedFeature", NOTHING_SELECTED);
    }
  }
}, Cesium.ScreenSpaceEventType.LEFT_CLICK);

// UI ============================================================================

Sandcastle.addToggleButton("Enable picking", enablePicking, function (checked) {
  enablePicking = checked;
});

const demos = [
  {
    text: "Show Classification",
    onselect: showClassification,
  },
  {
    text: "Show Alternative Classification",
    onselect: showAlternativeClassification,
  },
  {
    text: "Translucent Windows",
    onselect: translucentWindows,
  },
  {
    text: "Stylized PBR Materials",
    onselect: pbrMaterials,
  },
  {
    text: "Golden Touch",
    onselect: goldenTouch,
  },
  {
    text: "Multiple Feature ID Sets",
    onselect: multipleFeatureIds,
  },
  {
    text: "No Classification",
    onselect: showPhotogrammetry,
  },
];
Sandcastle.addDefaultToolbarMenu(demos);
showClassification();
